/**
 Created by:
 muhtearjan mehmood مۇختەرجان مەخمۇت (Email: yeganaaa@163.com)
 At 8/5/20 4:06 AM
 */

export enum MetaType {
    RpcMethod = 1,
    RpcModule = 2,
}
export abstract class MetaData {
    abstract type: MetaType
    public constructor(public name: string) {
    }

    public static detectFromObject(object: any): MetaData {
        return _detect(object)
    }
    public static typeSafeDataFromObject(object: any) : MetaData {
        return _from(object)
    }
}
export class MetaOfMethod extends MetaData{
    type: MetaType = MetaType.RpcMethod
    public constructor(name: string) {
        super(name)
    }
}
export class MetaOfModule extends MetaData {
    type: MetaType = MetaType.RpcModule
    public constructor(name: string, public children: MetaData[]) {
        super(name);
    }
}

function _detect(object: any): MetaData {
    const metaData = new MetaOfModule("", [])
    function filter(property: any): boolean {
        const type = typeof(property)
        let valid = (type == "function" || type == "object")
        valid = valid && property.constructor.name != "Array"
        return valid
    }
    function _objectMetaIntoMetaData(obj: any, meta: MetaOfModule) {
        if (!filter(obj)) return
        const keys: string[] = Object.keys(obj)
        if (obj.constructor.name != "Object") {
            keys.push(...Object.getOwnPropertyNames(obj.constructor.prototype).filter(key => key != "constructor"))
        }
        keys.filter(key => !key.startsWith("_")).filter(key => filter(obj[key])).forEach(key => {
            const type = typeof(obj[key])
            if (type == "function") {
                meta.children.push(new MetaOfMethod(key))
            }
            if (type == "object") {
                const propertyMeta = new MetaOfModule(key, []);
                _objectMetaIntoMetaData(obj[key], propertyMeta)
                meta.children.push(propertyMeta)
            }
        })
    }
    _objectMetaIntoMetaData(object, metaData)
    return metaData
}
function _from(object: any): MetaData {
    let meta = new MetaOfMethod(object.name)
    if (object.type == MetaType.RpcModule) {
        meta = new MetaOfModule(object.name, object.children.map((item: any) => _from(item)))
    }
    return meta
}